package org.koin.core.scope;

import org.koin.core.annotation.KoinInternalApi;
import org.koin.core.definition.BeanDefinition;
import org.koin.core.definition.Definition;
import org.koin.core.definition.Definitions;
import org.koin.core.definition.Options;
import org.koin.core.error.DefinitionOverrideException;
import org.koin.core.parameter.DefinitionParameters;
import org.koin.core.qualifier.Qualifier;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;

public class ScopeDefinition {

    public static final String ROOT_SCOPE_ID = "-Root-";
    public static final Qualifier ROOT_SCOPE_QUALIFIER = Qualifier._q(ROOT_SCOPE_ID);

    private Qualifier qualifier;
    private boolean isRoot;

    @KoinInternalApi
    private HashSet<BeanDefinition> definitions = new HashSet<>();

    public ScopeDefinition(Qualifier qualifier, boolean isRoot) {
        this.qualifier = qualifier;
        this.isRoot = isRoot;
    }

    public ScopeDefinition(Qualifier qualifier) {
        this.qualifier = qualifier;
        this.isRoot = false;
    }

    public static ScopeDefinition rootDefinition() {
        return new ScopeDefinition(ROOT_SCOPE_QUALIFIER, true);
    }

    public void save(BeanDefinition beanDefinition, boolean forceOverride)
            throws DefinitionOverrideException {
        if (definitions.contains(beanDefinition)) {
            if (beanDefinition.getOptions().isOverride() || forceOverride) {
                definitions.remove(beanDefinition);
            } else {
                Iterator<BeanDefinition> iterator = definitions.iterator();
                BeanDefinition current = null;
                while (iterator.hasNext()) {
                    BeanDefinition item = iterator.next();
                    if (item.equals(beanDefinition)) {
                        current = item;
                        break;
                    }
                }
                throw new DefinitionOverrideException(
                        "Definition '" + beanDefinition
                                + "' try to override existing definition. "
                                + "Please use override option or check for definition '"
                                + current + "'"
                );
            }
        }
        definitions.add(beanDefinition);
    }

    public void save(BeanDefinition beanDefinition) throws DefinitionOverrideException {
        this.save(beanDefinition, false);
    }

    public void remove(BeanDefinition beanDefinition) {
        definitions.remove(beanDefinition);
    }

    public int size() {
        return definitions.size();
    }

    public <T> BeanDefinition declareNewDefinition(
            Class<T> clazz,
            T instance,
            Qualifier defQualifier,
            List<Class> secondaryTypes,
            boolean override
    ) throws DefinitionOverrideException {

        Iterator<BeanDefinition> iterator = definitions.iterator();
        BeanDefinition found = null;
        while (iterator.hasNext()) {
            BeanDefinition def = iterator.next();
            boolean isFound = def.is(clazz, defQualifier, qualifier);
            if (isFound) {
                found = def;
                break;
            }
        }
        if (found != null) {
            if (override) {
                remove(found);
            } else {
                throw new DefinitionOverrideException(
                        "Trying to override existing definition '" +
                                found + "' with new definition typed '" + clazz + "'"
                );
            }
        }

        Options options = new Options()
                .setCreatedAtStart(false)
                .setOverride(override)
                .setExtraDefinition(true);

        BeanDefinition beanDefinition = Definitions.INSTANCE().createSingle(
                clazz,
                defQualifier,
                new Definition() {
                    @Override
                    public Object invoke(Scope scope, DefinitionParameters parameters) {
                        return instance;
                    }
                },
                options,
                secondaryTypes == null ? new ArrayList<>() : secondaryTypes,
                qualifier
        );
        save(beanDefinition, override);
        return beanDefinition;
    }

    public void unloadDefinition(BeanDefinition beanDefinition) {
        definitions.remove(beanDefinition);
    }

    public void removeExtras() {
        ArrayList extras = new ArrayList();
        for (BeanDefinition item : definitions) {
            if (item.getOptions().isExtraDefinition()) {
                extras.add(item);
            }
        }
        definitions.removeAll(extras);
    }

    public HashSet<BeanDefinition> getDefinitions() {
        return definitions;
    }

    public void setDefinitions(HashSet<BeanDefinition> definitions) {
        this.definitions = definitions;
    }

    public boolean isRoot() {
        return isRoot;
    }

    public void setRoot(boolean root) {
        isRoot = root;
    }

    public Qualifier getQualifier() {
        return qualifier;
    }
}
